import os
import json
import tempfile
import shutil
import traceback
import logging
import paramiko
import socket
import subprocess
import time
from datetime import datetime
from django.conf import settings
from .models import AnsibleConfig, AnsibleGlobalSetting

logger = logging.getLogger(__name__)


class AnsibleConnectionManager:
    """Ansible连接管理器"""

    def __init__(self, config):
        self.config = config

    def upload_file_to_remote(self, local_content, remote_path):
        """将文件内容上传到远程主机"""
        try:
            # 创建SSH客户端
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

            # 根据认证方式连接
            if self.config.auth_type == 'password':
                ssh.connect(
                    hostname=self.config.host,
                    port=self.config.port,
                    username=self.config.username,
                    password=self.config.password,
                    timeout=10
                )
            else:  # 密钥认证
                with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.pem') as key_file:
                    key_file.write(self.config.private_key)
                    key_path = key_file.name

                # 设置正确的权限
                os.chmod(key_path, 0o600)

                try:
                    ssh.connect(
                        hostname=self.config.host,
                        port=self.config.port,
                        username=self.config.username,
                        key_filename=key_path,
                        timeout=10
                    )
                finally:
                    # 清理临时文件
                    os.unlink(key_path)

            # 创建SFTP客户端
            sftp = ssh.open_sftp()

            # 确保远程目录存在
            remote_dir = os.path.dirname(remote_path)
            if remote_dir:
                try:
                    # 尝试创建目录（如果不存在）
                    ssh.exec_command(f'mkdir -p {remote_dir}')
                except:
                    pass

            # 创建临时文件写入内容
            with tempfile.NamedTemporaryFile(mode='w', delete=False, encoding='utf-8') as temp_file:
                temp_file.write(local_content)
                temp_local_path = temp_file.name

            try:
                # 上传文件
                sftp.put(temp_local_path, remote_path)

                # 设置文件权限
                sftp.chmod(remote_path, 0o644)

            finally:
                # 清理临时文件
                os.unlink(temp_local_path)

            sftp.close()
            ssh.close()

            return {
                'status': 'success',
                'message': f'文件已成功上传到 {self.config.host}:{remote_path}'
            }

        except paramiko.AuthenticationException:
            return {
                'status': 'error',
                'message': f'认证失败：无法连接到 {self.config.host}'
            }
        except paramiko.SSHException as e:
            return {
                'status': 'error',
                'message': f'SSH连接错误：{str(e)}'
            }
        except Exception as e:
            return {
                'status': 'error',
                'message': f'文件上传失败：{str(e)}'
            }

    def test_connection(self):
        """测试Ansible配置的连接"""
        try:
            # 创建SSH客户端
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

            # 根据认证方式连接
            if self.config.auth_type == 'password':
                ssh.connect(
                    hostname=self.config.host,
                    port=self.config.port,
                    username=self.config.username,
                    password=self.config.password,
                    timeout=10,
                    allow_agent=False,
                    look_for_keys=False
                )
            elif self.config.auth_type == 'key':
                # 创建临时私钥文件
                with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.pem') as key_file:
                    key_file.write(self.config.private_key)
                    key_path = key_file.name

                # 设置正确的权限
                os.chmod(key_path, 0o600)

                try:
                    ssh.connect(
                        hostname=self.config.host,
                        port=self.config.port,
                        username=self.config.username,
                        key_filename=key_path,
                        timeout=10,
                        allow_agent=False,
                        look_for_keys=False
                    )
                finally:
                    # 清理临时文件
                    os.unlink(key_path)

            # 测试基本命令
            stdin, stdout, stderr = ssh.exec_command('echo "connection test"')
            result = stdout.read().decode().strip()

            ssh.close()

            if result == "connection test":
                return {
                    'status': 'success',
                    'message': '连接测试成功'
                }
            else:
                return {
                    'status': 'error',
                    'message': '连接测试失败：命令执行异常'
                }

        except paramiko.AuthenticationException:
            return {
                'status': 'error',
                'message': '认证失败：用户名或密码/密钥错误'
            }
        except paramiko.SSHException as e:
            return {
                'status': 'error',
                'message': f'SSH连接错误：{str(e)}'
            }
        except socket.timeout:
            return {
                'status': 'error',
                'message': '连接超时：无法连接到目标主机'
            }
        except Exception as e:
            logger.error(f"连接测试失败: {str(e)}")
            return {
                'status': 'error',
                'message': f'连接失败：{str(e)}'
            }

    def check_ansible_installation(self):
        """检查Ansible是否已安装"""
        try:
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

            # 连接到服务器
            if self.config.auth_type == 'password':
                ssh.connect(
                    hostname=self.config.host,
                    port=self.config.port,
                    username=self.config.username,
                    password=self.config.password,
                    timeout=10
                )
            elif self.config.auth_type == 'key':
                with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.pem') as key_file:
                    key_file.write(self.config.private_key)
                    key_path = key_file.name

                try:
                    ssh.connect(
                        hostname=self.config.host,
                        port=self.config.port,
                        username=self.config.username,
                        key_filename=key_path,
                        timeout=10
                    )
                finally:
                    os.unlink(key_path)

            # 检查ansible命令
            stdin, stdout, stderr = ssh.exec_command('ansible --version')
            version_output = stdout.read().decode()
            error_output = stderr.read().decode()

            ssh.close()

            if version_output and 'ansible' in version_output.lower():
                # 解析版本信息
                lines = version_output.strip().split('\n')
                version_line = lines[0] if lines else ''

                return {
                    'status': 'success',
                    'installed': True,
                    'version': version_line,
                    'details': version_output
                }
            else:
                return {
                    'status': 'error',
                    'installed': False,
                    'message': 'Ansible未安装或不在PATH中',
                    'error': error_output
                }

        except Exception as e:
            logger.error(f"检查Ansible安装失败: {str(e)}")
            return {
                'status': 'error',
                'installed': False,
                'message': f'检查失败：{str(e)}'
            }

    def get_inventory_content(self):
        """获取inventory文件内容"""
        try:
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

            # 连接到服务器
            if self.config.auth_type == 'password':
                ssh.connect(
                    hostname=self.config.host,
                    port=self.config.port,
                    username=self.config.username,
                    password=self.config.password,
                    timeout=10
                )
            elif self.config.auth_type == 'key':
                with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.pem') as key_file:
                    key_file.write(self.config.private_key)
                    key_path = key_file.name

                try:
                    ssh.connect(
                        hostname=self.config.host,
                        port=self.config.port,
                        username=self.config.username,
                        key_filename=key_path,
                        timeout=10
                    )
                finally:
                    os.unlink(key_path)

            # 读取inventory文件
            stdin, stdout, stderr = ssh.exec_command(f'cat {self.config.inventory_path}')
            content = stdout.read().decode()
            error = stderr.read().decode()

            ssh.close()

            if error and 'No such file' in error:
                # 文件不存在，创建默认内容
                return {
                    'status': 'success',
                    'content': '# Ansible inventory file\n# Managed by DevOps Platform\n\n[ungrouped]\n',
                    'file_exists': False
                }
            elif error:
                return {
                    'status': 'error',
                    'message': f'读取inventory文件失败：{error}'
                }
            else:
                return {
                    'status': 'success',
                    'content': content,
                    'file_exists': True
                }

        except Exception as e:
            logger.error(f"获取inventory内容失败: {str(e)}")
            return {
                'status': 'error',
                'message': f'获取失败：{str(e)}'
            }

    def execute_adhoc_command(self, inventory_content, module_name, module_args, pattern, credential=None):
        """执行Ansible Ad-hoc命令（通过SSH连接到远程ansible主机执行）"""
        ssh = None
        sftp = None
        remote_inventory_path = None
        remote_key_path = None

        try:
            logger.info(f"开始在远程主机 {self.config.host} 执行Ansible Ad-hoc命令: {module_name} {module_args} on {pattern}")

            # 建立SSH连接到ansible配置的远程主机
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

            # 连接到远程主机
            ssh.connect(
                hostname=self.config.host,
                port=self.config.port,
                username=self.config.username,
                password=self.config.password,
                timeout=30
            )

            sftp = ssh.open_sftp()

            # 在远程主机创建临时inventory文件
            remote_inventory_path = f'/tmp/ansible_inventory_{int(time.time())}.ini'
            with sftp.open(remote_inventory_path, 'w') as inv_file:
                inv_file.write(inventory_content)

            logger.info(f"在远程主机创建临时inventory文件: {remote_inventory_path}")
            logger.debug(f"Inventory内容:\n{inventory_content}")

            # 简单直接：只使用配置中的密码认证
            auth_env_vars = [f'ANSIBLE_SSH_PASS={self.config.password}']
            logger.info(f"使用配置密码认证，用户: {self.config.username}")

            # 构建ansible命令 - 在远程主机执行
            cmd_parts = ['ansible', pattern, '-i', remote_inventory_path, '-m', module_name]

            # 添加模块参数
            if module_args:
                cmd_parts.extend(['-a', f'"{module_args}"'])

            # 添加用户认证参数 - 直接使用配置中的用户名
            cmd_parts.extend(['-u', self.config.username])

            # 添加其他参数
            cmd_parts.extend(['-o'])  # 单行输出
            cmd_parts.extend(['-T', '30'])  # 30秒超时

            # 构建完整的命令字符串，包含环境变量
            env_vars = [
                'ANSIBLE_HOST_KEY_CHECKING=False',
                'ANSIBLE_TIMEOUT=30',
                'ANSIBLE_CONNECT_TIMEOUT=10',
                'ANSIBLE_SSH_RETRIES=3',
                'ANSIBLE_PIPELINING=True'
            ]

            # 添加密码认证环境变量
            env_vars.extend(auth_env_vars)

            # 构建完整的远程执行命令
            env_string = ' '.join(env_vars)
            ansible_cmd = ' '.join(cmd_parts)
            full_cmd = f"{env_string} {ansible_cmd}"

            logger.info(f"在远程主机执行Ansible命令: {full_cmd}")

            # 在远程主机执行ansible命令
            stdin, stdout, stderr = ssh.exec_command(full_cmd, timeout=300)

            # 读取输出
            stdout_content = stdout.read().decode('utf-8', errors='ignore')
            stderr_content = stderr.read().decode('utf-8', errors='ignore')

            # 安全地获取退出码
            try:
                exit_code = stdout.channel.recv_exit_status()
                if exit_code is None:
                    exit_code = -1  # 如果无法获取退出码，设为-1表示未知错误
            except Exception as e:
                logger.error(f"获取命令退出码失败: {str(e)}")
                exit_code = -1

            logger.info(f"命令执行完成，退出码: {exit_code}")
            logger.info(f"标准输出长度: {len(stdout_content)}")
            logger.info(f"错误输出长度: {len(stderr_content)}")

            if stdout_content:
                logger.info(f"标准输出:\n{stdout_content}")
            if stderr_content:
                logger.info(f"错误输出:\n{stderr_content}")

            # 解析ansible输出结果
            stats = self._parse_adhoc_output(stdout_content, stderr_content, exit_code)

            return stats

        except Exception as e:
                logger.error(f"执行Ansible Ad-hoc命令失败: {str(e)}")
                return {
                    'status': 'failed',
                    'message': f'执行失败：{str(e)}',
                    'stdout': '',
                    'stderr': str(e),
                    'result': {'failed': 1, 'ok': 0, 'changed': 0, 'unreachable': 0}
                }

        finally:
            # 清理远程临时文件
            try:
                if ssh and remote_inventory_path:
                    ssh.exec_command(f'rm -f {remote_inventory_path}')
                if ssh and remote_key_path:
                    ssh.exec_command(f'rm -f {remote_key_path}')
            except:
                pass

            # 关闭连接
            try:
                if sftp:
                    sftp.close()
                if ssh:
                    ssh.close()
            except:
                pass

    def _parse_adhoc_output(self, stdout, stderr, exit_code):
        """解析Ansible Ad-hoc命令输出"""
        try:
            # 初始化统计信息
            stats = {
                'status': 'success' if exit_code == 0 else 'failed',
                'stdout': stdout,
                'stderr': stderr,
                'exit_code': exit_code,
                'result': {
                    'ok': 0,
                    'changed': 0,
                    'unreachable': 0,
                    'failed': 0,
                    'skipped': 0
                }
            }

            # 解析输出中的主机状态
            if stdout:
                lines = stdout.split('\n')
                for line in lines:
                    line = line.strip()
                    if ' | SUCCESS ' in line:
                        stats['result']['ok'] += 1
                    elif ' | CHANGED ' in line:
                        stats['result']['changed'] += 1
                    elif ' | FAILED ' in line:
                        stats['result']['failed'] += 1
                    elif ' | UNREACHABLE ' in line:
                        stats['result']['unreachable'] += 1

            return stats

        except Exception as e:
            logger.error(f"解析Ansible输出失败: {str(e)}")
            return {
                'status': 'failed',
                'stdout': stdout,
                'stderr': stderr,
                'exit_code': exit_code,
                'result': {'failed': 1, 'ok': 0, 'changed': 0, 'unreachable': 0, 'skipped': 0}
            }
    def execute_playbook(self, inventory_content, playbook_content, credential=None, extra_vars=None, limit=None, tags=None):
        """执行Ansible Playbook（使用JSON callback获取详细输出）"""
        ssh = None
        sftp = None
        remote_inventory_path = None
        remote_playbook_path = None

        try:
            logger.info(f"开始在远程主机 {self.config.host} 执行Ansible Playbook")

            # 建立SSH连接到ansible配置的远程主机
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

            # 连接到远程主机
            ssh.connect(
                hostname=self.config.host,
                port=self.config.port,
                username=self.config.username,
                password=self.config.password,
                timeout=30
            )

            sftp = ssh.open_sftp()

            # 在远程主机创建临时inventory文件
            remote_inventory_path = f'/tmp/ansible_inventory_{int(time.time())}.ini'
            with sftp.open(remote_inventory_path, 'w') as inv_file:
                inv_file.write(inventory_content)

            # 在远程主机创建临时playbook文件
            remote_playbook_path = f'/tmp/ansible_playbook_{int(time.time())}.yml'
            with sftp.open(remote_playbook_path, 'w') as pb_file:
                pb_file.write(playbook_content)

            logger.info(f"在远程主机创建临时文件: inventory={remote_inventory_path}, playbook={remote_playbook_path}")

            # 简单直接：只使用配置中的密码认证
            auth_env_vars = [f'ANSIBLE_SSH_PASS={self.config.password}']
            logger.info(f"使用配置密码认证，用户: {self.config.username}")

            # 构建ansible-playbook命令 - 需要verbose才能获取JSON详情
            cmd_parts = ['ansible-playbook', remote_playbook_path, '-i', remote_inventory_path]

            # 使用-v参数获取JSON详情，但避免过多调试信息
            cmd_parts.extend(['-v'])

            # 添加用户认证参数 - 直接使用配置中的用户名
            cmd_parts.extend(['-u', self.config.username])

            # 添加extra_vars参数
            if extra_vars:
                for key, value in extra_vars.items():
                    cmd_parts.extend(['-e', f'{key}={value}'])

            # 添加limit参数
            if limit:
                cmd_parts.extend(['-l', limit])

            # 添加tags参数
            if tags:
                cmd_parts.extend(['-t', tags])

            # 构建完整的命令字符串，包含环境变量
            env_vars = [
                'ANSIBLE_HOST_KEY_CHECKING=False',
                'ANSIBLE_TIMEOUT=30',
                'ANSIBLE_CONNECT_TIMEOUT=10',
                'ANSIBLE_SSH_RETRIES=3',
                'ANSIBLE_PIPELINING=True'
            ]

            # 添加凭据相关的环境变量
            env_vars.extend(auth_env_vars)

            # 构建完整的远程执行命令
            env_string = ' '.join(env_vars)
            ansible_cmd = ' '.join(cmd_parts)
            full_cmd = f"{env_string} {ansible_cmd}"

            logger.info(f"在远程主机执行Ansible Playbook命令: {full_cmd}")

            # 在远程主机执行ansible-playbook命令，完全模拟subprocess.run的行为
            stdin, stdout, stderr = ssh.exec_command(full_cmd, timeout=600)  # 10分钟超时

            # 读取输出，完全按照subprocess.run的方式处理
            stdout_content = stdout.read().decode('utf-8', errors='ignore')
            stderr_content = stderr.read().decode('utf-8', errors='ignore')

            # 安全地获取退出码
            try:
                exit_code = stdout.channel.recv_exit_status()
                if exit_code is None:
                    exit_code = -1  # 如果无法获取退出码，设为-1表示未知错误
            except Exception as e:
                logger.error(f"获取Playbook命令退出码失败: {str(e)}")
                exit_code = -1

            logger.info(f"Playbook执行完成，退出码: {exit_code}")
            logger.info(f"标准输出长度: {len(stdout_content)}")
            logger.info(f"错误输出长度: {len(stderr_content)}")

            if stdout_content:
                logger.debug(f"标准输出:\n{stdout_content}")
            if stderr_content:
                logger.debug(f"错误输出:\n{stderr_content}")

            # 解析playbook输出结果
            stats = self._parse_playbook_output(stdout_content, stderr_content, exit_code)

            return stats

        except Exception as e:
            logger.error(f"执行Ansible Playbook失败: {str(e)}")
            return {
                'status': 'failed',
                'message': f'执行失败：{str(e)}',
                'stdout': '',
                'stderr': str(e),
                'result': {'failed': 1, 'ok': 0, 'changed': 0, 'unreachable': 0}
            }

        finally:
            # 清理远程临时文件
            try:
                if ssh and remote_inventory_path:
                    ssh.exec_command(f'rm -f {remote_inventory_path}')
                if ssh and remote_playbook_path:
                    ssh.exec_command(f'rm -f {remote_playbook_path}')
            except:
                pass

            # 关闭连接
            try:
                if sftp:
                    sftp.close()
                if ssh:
                    ssh.close()
            except:
                pass

    def _parse_playbook_output(self, stdout, stderr, exit_code):
        """解析Ansible Playbook输出，返回前端期望的格式"""
        try:
            # 初始化统计信息
            stats = {
                'status': 'success' if exit_code == 0 else 'failed',
                'stdout': stdout,
                'stderr': stderr,
                'exit_code': exit_code,
                'result': {
                    'ok': 0,
                    'changed': 0,
                    'unreachable': 0,
                    'failed': 0,
                    'skipped': 0
                },
                'new_output': []  # 前端期望的格式化输出
            }

            # 解析输出并分类每一行
            if stdout:
                lines = stdout.split('\n')
                for line_index, line in enumerate(lines):
                    original_line = line
                    line = line.strip()

                    if not line:
                        continue

                    # 确定行类型和颜色
                    line_type = 'output'  # 默认类型
                    details = None  # 初始化details

                    # PLAY 开始
                    if line.startswith('PLAY ['):
                        line_type = 'play'
                    # TASK 开始
                    elif line.startswith('TASK ['):
                        line_type = 'task'
                    # 主机执行结果 - 匹配Ansible verbose输出格式
                    elif line.startswith('ok: [') and ']' in line:
                        line_type = 'ok'
                        stats['result']['ok'] += 1
                        # 提取主机名和格式化显示
                        hostname = line.split('[')[1].split(']')[0] if '[' in line and ']' in line else 'unknown'
                        line = f"{hostname} | ok"
                        # 解析详细信息
                        details = self._extract_task_details(original_line, lines, line_index)
                    elif line.startswith('changed: [') and ']' in line:
                        line_type = 'changed'
                        stats['result']['changed'] += 1
                        # 提取主机名和格式化显示
                        hostname = line.split('[')[1].split(']')[0] if '[' in line and ']' in line else 'unknown'
                        line = f"{hostname} | changed"
                        # 解析详细信息
                        details = self._extract_task_details(original_line, lines, line_index)
                    elif line.startswith('failed: [') or line.startswith('fatal: ['):
                        line_type = 'failed'
                        stats['result']['failed'] += 1
                        # 提取主机名和格式化显示
                        hostname = line.split('[')[1].split(']')[0] if '[' in line and ']' in line else 'unknown'
                        line = f"{hostname} | failed"
                        # 解析详细信息
                        details = self._extract_task_details(original_line, lines, line_index)
                    elif line.startswith('unreachable: ['):
                        line_type = 'failed'  # 前端将unreachable也显示为failed颜色
                        stats['result']['unreachable'] += 1
                        # 提取主机名和格式化显示
                        hostname = line.split('[')[1].split(']')[0] if '[' in line and ']' in line else 'unknown'
                        line = f"{hostname} | unreachable"
                        # 解析详细信息
                        details = self._extract_task_details(line, lines, lines.index(original_line) if original_line in lines else 0)
                    # PLAY RECAP
                    elif line.startswith('PLAY RECAP'):
                        line_type = 'recap'
                    # 主机统计信息
                    elif ':' in line and 'ok=' in line:
                        line_type = 'host_stats'
                        # 解析统计信息
                        parts = line.split()
                        for part in parts:
                            if '=' in part:
                                key, value = part.split('=', 1)
                                if key in stats['result']:
                                    try:
                                        # 注意：这里不要重复累加，因为上面已经按行累加过了
                                        pass
                                    except ValueError:
                                        pass

                    # 只添加重要的行到输出列表，过滤调试信息
                    if line_type in ['play', 'task', 'ok', 'changed', 'failed', 'recap', 'host_stats']:
                        output_item = {
                            'content': line if line_type in ['ok', 'changed', 'failed'] else original_line,
                            'type': line_type
                        }

                        # 如果有详细信息，添加details字段
                        if details:
                            output_item['details'] = details

                        stats['new_output'].append(output_item)

            # 如果有错误输出，也添加到new_output中
            if stderr:
                for line in stderr.split('\n'):
                    if line.strip():
                        stats['new_output'].append({
                            'content': line,
                            'type': 'failed'
                        })

            return stats

        except Exception as e:
            logger.error(f"解析Playbook输出失败: {str(e)}")
            return {
                'status': 'failed',
                'stdout': stdout,
                'stderr': stderr,
                'exit_code': exit_code,
                'result': {'failed': 1, 'ok': 0, 'changed': 0, 'unreachable': 0, 'skipped': 0},
                'new_output': [
                    {'content': f'解析失败: {str(e)}', 'type': 'failed'}
                ]
            }

    def _extract_task_details(self, result_line, all_lines, current_index):
        """提取任务执行的详细信息"""
        try:
            details = {}

            # 解析主机名 - 处理Ansible原生格式：changed: [devops1] => {
            if ': [' in result_line and ']' in result_line:
                start = result_line.find('[') + 1
                end = result_line.find(']', start)
                if start > 0 and end > start:
                    hostname = result_line[start:end]
                    details['hostname'] = hostname

            # 收集多行JSON输出
            json_lines = []

            # 检查是否有JSON输出 - 可能在当前行或后续行
            has_json = False
            json_start_line = current_index

            # 情况1：当前行包含 => {
            if ' => {' in result_line:
                has_json = True
                logger.debug(f"发现JSON开始行: {result_line}")
            else:
                # 情况2：检查后续几行是否有JSON开始
                for i in range(current_index + 1, min(current_index + 5, len(all_lines))):
                    if i < len(all_lines):
                        next_line = all_lines[i].strip()
                        if next_line.startswith('{') or ' => {' in next_line:
                            has_json = True
                            json_start_line = i
                            logger.debug(f"在第{i}行发现JSON: {next_line}")
                            break

            if has_json:
                # 从JSON开始行收集
                if ' => {' in result_line:
                    # 情况1：当前行包含 => {，从 { 开始提取
                    json_start = result_line.find('{')
                    if json_start != -1:
                        json_lines.append(result_line[json_start:])
                        start_index = current_index + 1
                    else:
                        start_index = json_start_line
                else:
                    # 情况2：从后续行开始
                    start_index = json_start_line

                # 继续收集JSON行，直到遇到完整的 }
                brace_count = 0
                # 计算第一行的大括号数量
                if json_lines:
                    first_line = json_lines[0]
                    brace_count += first_line.count('{') - first_line.count('}')

                for i in range(start_index, min(start_index + 50, len(all_lines))):
                    if i < len(all_lines):
                        next_line = all_lines[i].strip()
                        # 检查是否是停止条件
                        if (next_line.startswith(('changed:', 'ok:', 'failed:', 'TASK [', 'PLAY [', 'PLAY RECAP')) or
                            next_line.startswith('*' * 10)):  # 任务分隔线
                            # 遇到新的任务或结果行，停止收集
                            break
                        elif next_line and not next_line.startswith(('changed:', 'ok:', 'failed:')):
                            json_lines.append(next_line)
                            # 计算大括号平衡
                            brace_count += next_line.count('{') - next_line.count('}')
                            # 如果大括号平衡，JSON结束
                            if brace_count <= 0:
                                break

                # 尝试解析完整的JSON
                try:
                    import json
                    json_text = '\n'.join(json_lines)
                    json_data = json.loads(json_text)

                    # 直接将JSON数据的字段提取到details顶层，符合前端期望的数据结构
                    details.update(json_data)  # 将所有JSON字段直接合并到details中

                    # 保存原始JSON供调试
                    details['_raw_json'] = json_text

                except (json.JSONDecodeError, ValueError) as e:
                    # 如果JSON解析失败，保存原始文本和错误信息
                    details['raw_output'] = '\n'.join(json_lines)
                    details['parse_error'] = str(e)
                    logger.error(f"JSON解析失败: {str(e)}, 原始文本: {json_text[:200]}...")

            # 查找多行输出（通常在结果行之后的缩进行）
            multiline_output = []
            for i in range(current_index + 1, min(current_index + 10, len(all_lines))):
                if i < len(all_lines):
                    next_line = all_lines[i]
                    # 如果是缩进行或者空行，可能是详细输出的一部分
                    if next_line.startswith('    ') or next_line.startswith('\t') or not next_line.strip():
                        if next_line.strip():
                            multiline_output.append(next_line.strip())
                    else:
                        # 遇到非缩进行，停止
                        break

            if multiline_output:
                details['multiline_output'] = '\n'.join(multiline_output)

            # 如果没有找到JSON，但有主机信息，添加基本信息
            if 'result' not in details and 'hostname' in details:
                # 从原始行提取状态信息
                if 'changed:' in result_line:
                    details['status'] = 'changed'
                    details['changed'] = True
                elif 'ok:' in result_line:
                    details['status'] = 'ok'
                    details['changed'] = False
                elif 'failed:' in result_line or 'fatal:' in result_line:
                    details['status'] = 'failed'
                    details['failed'] = True

                # 添加原始行作为输出
                details['raw_line'] = result_line
                details['message'] = f"任务在主机 {details['hostname']} 上执行"

            return details if details else None

        except Exception as e:
            logger.error(f"提取任务详细信息失败: {str(e)}")
            return None

        except Exception as e:
            logger.error(f"解析Playbook输出失败: {str(e)}")
            return {
                'status': 'failed',
                'message': f'解析失败：{str(e)}',
                'stdout': stdout,
                'stderr': stderr,
                'result': {'failed': 1, 'ok': 0, 'changed': 0, 'unreachable': 0},
                'exit_code': exit_code
            }

    def _parse_ansible_output(self, stdout, stderr, return_code):
        """解析Ansible输出结果"""
        try:
            # 初始化结果统计
            result_stats = {
                'ok': 0, 'changed': 0, 'unreachable': 0,
                'failed': 0, 'skipped': 0, 'rescued': 0, 'ignored': 0
            }

            # 解析ad-hoc命令的输出（单行格式）
            # 格式如: devops01 | SUCCESS => {"changed": false,"ping": "pong"}
            # 或: devops01 | CHANGED | rc=0 | (stdout) ...
            for line in stdout.split('\n'):
                line = line.strip()
                if '|' in line:
                    if '| SUCCESS =>' in line or '| SUCCESS |' in line:
                        result_stats['ok'] += 1
                        # 检查是否有变更
                        if '"changed": true' in line or '"changed":true' in line:
                            result_stats['changed'] += 1
                    elif '| CHANGED |' in line or '| CHANGED =>' in line:
                        result_stats['ok'] += 1
                        result_stats['changed'] += 1
                    elif '| FAILED =>' in line or '| FAILED |' in line:
                        result_stats['failed'] += 1
                    elif '| UNREACHABLE =>' in line or '| UNREACHABLE |' in line or '| UNREACHABLE!' in line:
                        result_stats['unreachable'] += 1

            # 如果没有解析到结果，尝试解析PLAY RECAP部分（playbook格式）
            if sum(result_stats.values()) == 0 and 'PLAY RECAP' in stdout:
                recap_section = stdout.split('PLAY RECAP')[1] if 'PLAY RECAP' in stdout else ''
                for line in recap_section.split('\n'):
                    if ':' in line and 'ok=' in line:
                        # 解析统计信息
                        parts = line.split()
                        for part in parts:
                            if '=' in part:
                                key, value = part.split('=')
                                if key in result_stats:
                                    try:
                                        result_stats[key] += int(value)
                                    except ValueError:
                                        pass

            # 确定执行状态
            if return_code == 0:
                if result_stats['failed'] > 0 or result_stats['unreachable'] > 0:
                    status = 'failed'
                else:
                    status = 'success'
            else:
                status = 'failed'

            # 如果没有解析到任何主机结果，但有错误输出，说明连接失败
            if sum(result_stats.values()) == 0 and stderr:
                result_stats['unreachable'] = 1  # 假设至少有一个主机不可达

            return {
                'status': status,
                'message': '执行完成' if status == 'success' else '执行失败',
                'stdout': stdout,
                'stderr': stderr,
                'result': result_stats,
                'return_code': return_code
            }

        except Exception as e:
            logger.error(f"解析Ansible输出失败: {str(e)}")
            return {
                'status': 'failed',
                'message': f'解析失败：{str(e)}',
                'stdout': stdout,
                'stderr': stderr,
                'result': {'failed': 1, 'ok': 0, 'changed': 0, 'unreachable': 0},
                'return_code': return_code
            }



class AnsibleHealthChecker:
    """Ansible健康检查器"""

    @staticmethod
    def check_config_health(config):
        """检查单个配置的健康状态"""
        try:
            manager = AnsibleConnectionManager(config)

            # 1. 测试连接
            conn_result = manager.test_connection()
            if conn_result['status'] != 'success':
                return {
                    'status': 'unhealthy',
                    'message': f"连接失败：{conn_result['message']}",
                    'details': {
                        'connection': conn_result,
                        'ansible': None,
                        'inventory': None
                    }
                }

            # 2. 检查Ansible安装
            ansible_result = manager.check_ansible_installation()
            if ansible_result['status'] != 'success' or not ansible_result.get('installed'):
                return {
                    'status': 'unhealthy',
                    'message': f"Ansible检查失败：{ansible_result['message']}",
                    'details': {
                        'connection': conn_result,
                        'ansible': ansible_result,
                        'inventory': None
                    }
                }

            # 3. 检查inventory文件
            inventory_result = manager.get_inventory_content()
            if inventory_result['status'] != 'success':
                return {
                    'status': 'warning',
                    'message': f"Inventory检查失败：{inventory_result['message']}",
                    'details': {
                        'connection': conn_result,
                        'ansible': ansible_result,
                        'inventory': inventory_result
                    }
                }

            # 所有检查通过
            return {
                'status': 'healthy',
                'message': '所有检查通过',
                'details': {
                    'connection': conn_result,
                    'ansible': ansible_result,
                    'inventory': inventory_result
                }
            }

        except Exception as e:
            logger.error(f"健康检查失败: {str(e)}")
            return {
                'status': 'error',
                'message': f'健康检查异常：{str(e)}',
                'details': None
            }

    @staticmethod
    def check_all_configs():
        """检查所有配置的健康状态"""
        configs = AnsibleConfig.objects.filter(is_active=True)
        results = []

        for config in configs:
            result = AnsibleHealthChecker.check_config_health(config)
            results.append({
                'config_id': config.id,
                'config_name': config.name,
                'result': result
            })

            # 更新配置的健康状态
            config.health_status = result['status']
            config.health_message = result['message']
            config.last_check_time = datetime.now()
            config.save()

        return results

    def _analyze_ansible_error(self, stderr, stdout):
        """分析Ansible执行错误"""
        error_analysis = {
            'error_type': 'unknown',
            'description': '未知错误',
            'suggestions': []
        }

        error_text = (stderr + stdout).lower()

        if 'failed to connect to the host via ssh' in error_text:
            error_analysis['error_type'] = 'ssh_connection_failed'
            error_analysis['description'] = 'SSH连接失败'
            error_analysis['suggestions'] = [
                '检查目标主机IP地址和端口是否正确',
                '确认目标主机SSH服务是否运行',
                '检查网络连通性',
                '验证防火墙设置'
            ]

            if 'permission denied' in error_text:
                error_analysis['suggestions'].extend([
                    '检查用户名和密码是否正确',
                    '确认SSH密钥是否正确配置',
                    '验证用户是否有SSH登录权限'
                ])

        elif 'unreachable' in error_text:
            error_analysis['error_type'] = 'host_unreachable'
            error_analysis['description'] = '主机不可达'
            error_analysis['suggestions'] = [
                '检查主机IP地址是否正确',
                '确认主机是否在线',
                '检查网络连通性',
                '验证inventory配置'
            ]

        elif 'authentication failed' in error_text or 'permission denied' in error_text:
            error_analysis['error_type'] = 'authentication_failed'
            error_analysis['description'] = '认证失败'
            error_analysis['suggestions'] = [
                '检查用户名和密码',
                '验证SSH密钥配置',
                '确认用户权限设置',
                '检查SSH配置文件'
            ]

        elif 'timeout' in error_text:
            error_analysis['error_type'] = 'connection_timeout'
            error_analysis['description'] = '连接超时'
            error_analysis['suggestions'] = [
                '增加连接超时时间',
                '检查网络延迟',
                '确认目标主机负载情况',
                '验证防火墙规则'
            ]

        elif 'module not found' in error_text or 'no such file or directory' in error_text:
            error_analysis['error_type'] = 'module_not_found'
            error_analysis['description'] = '模块或文件未找到'
            error_analysis['suggestions'] = [
                '检查模块名称是否正确',
                '确认目标主机上是否安装了所需软件',
                '验证文件路径是否存在',
                '检查权限设置'
            ]

        return error_analysis


class AnsibleGlobalSettingsManager:
    """Ansible全局设置管理器"""

    DEFAULT_SETTINGS = {
        'ansible_timeout': {
            'value': '30',
            'type': 'integer',
            'description': 'Ansible命令执行超时时间（秒）'
        },
        'max_concurrent_jobs': {
            'value': '5',
            'type': 'integer',
            'description': '最大并发任务数'
        },
        'log_level': {
            'value': 'INFO',
            'type': 'string',
            'description': '日志级别'
        },
        'enable_fact_caching': {
            'value': 'true',
            'type': 'boolean',
            'description': '启用事实缓存'
        },
        'default_inventory_path': {
            'value': '/etc/ansible/hosts',
            'type': 'string',
            'description': '默认inventory文件路径'
        }
    }

    @classmethod
    def initialize_default_settings(cls):
        """初始化默认设置"""
        for key, config in cls.DEFAULT_SETTINGS.items():
            setting, created = AnsibleGlobalSetting.objects.get_or_create(
                key=key,
                defaults={
                    'value': config['value'],
                    'setting_type': config['type'],
                    'description': config['description'],
                    'is_editable': True
                }
            )
            if created:
                logger.info(f"创建默认设置: {key}")

    @classmethod
    def get_setting(cls, key, default=None):
        """获取设置值"""
        try:
            setting = AnsibleGlobalSetting.objects.get(key=key)
            return setting.get_typed_value()
        except AnsibleGlobalSetting.DoesNotExist:
            return default

    @classmethod
    def set_setting(cls, key, value, setting_type='string', description=''):
        """设置值"""
        setting, created = AnsibleGlobalSetting.objects.get_or_create(
            key=key,
            defaults={
                'value': str(value),
                'setting_type': setting_type,
                'description': description,
                'is_editable': True
            }
        )
        if not created:
            setting.value = str(value)
            setting.save()
        return setting
